﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.IO;
using System.ComponentModel;

namespace LostLib.Programs
{
	public class GlobalAssemblyCache
	{
		static AssemblyDescription[] gacCache;

		public static AssemblyDescription[] GetAssemblies(bool cached)
		{
			if (!cached || (gacCache== null))
				gacCache = getAssemblies().ToArray();

			return gacCache;
		}

		public static AssemblyDescription[] GetAssemblies()
		{
			return GetAssemblies(false);
		}

		static List<AssemblyDescription> getAssemblies()
		{
			var parameters = new ProcessStartInfo("gacutil", "/nologo /l") {
				CreateNoWindow = true,
				ErrorDialog = false,
				RedirectStandardOutput = true,
				UseShellExecute = false,
				WindowStyle = ProcessWindowStyle.Hidden,
			};

			var result = new List<AssemblyDescription>();
			try
			{
				using (var gacutil = Process.Start(parameters))
				using (var output = gacutil.StandardOutput)
				{
					for (string line = output.ReadLine(); line != null; line = output.ReadLine())
					{
						try
						{
							line = line.Trim();
							if (String.IsNullOrEmpty(line)) continue;
							var name = new AssemblyDescription(line);
							result.Add(name);
						} catch (FileLoadException)
						{
						}
					}
				}
			} catch (Win32Exception error)
			{
				switch (error.ErrorCode)
				{
					// gacutil was not found
				case -2147467259:
					throw new FileNotFoundException("Can't start gacutil", "gacutil.exe");
				default:
					throw;
				}
			}

			return result;
		}

		public static void Add(string fileName)
		{
			if (fileName.Contains('"'))
				throw new ArgumentException("fileName", "File name can not contain \"");

			var parameters = new ProcessStartInfo("gacutil")
			{
				Arguments = string.Format("/nologo /i \"{0}\"", fileName),
				CreateNoWindow = true,
				ErrorDialog = false,
				UseShellExecute = false,
				WindowStyle = ProcessWindowStyle.Hidden,
				RedirectStandardOutput = true,
			};

			using (var gacutil = Process.Start(parameters))
			using (var err = gacutil.StandardOutput)
			{
				gacutil.WaitForExit();
				var error = err.ReadToEnd().Trim();
				if (gacutil.ExitCode != 0)
					throw new ApplicationException(error);
			}
		}

		public static void Remove(string assemblyName)
		{
			if (assemblyName.Contains('"'))
				throw new ArgumentException("assemblyName", "Assembly name can not contain \"");

			var parameters = new ProcessStartInfo("gacutil")
			{
				Arguments = string.Format("/nologo /u \"{0}\"", assemblyName),
				CreateNoWindow = true,
				ErrorDialog = false,
				UseShellExecute = false,
				WindowStyle = ProcessWindowStyle.Hidden,
				RedirectStandardOutput = true,
			};

			using (var gacutil = Process.Start(parameters))
			using (var err = gacutil.StandardOutput)
			{
				gacutil.WaitForExit();
				var error = err.ReadToEnd().Trim();
				if (gacutil.ExitCode != 0)
					throw new ApplicationException(error);
			}
		}

		public static void UpdateCache()
		{
			// CliAssemblyManager.WriteDebug("Updating GAC cache...");
			GlobalAssemblyCache.GetAssemblies();
			// CliAssemblyManager.WriteDebug("OK\n");
		}

		[Serializable]
		public class AssemblyDescription
		{
			public AssemblyDescription(string fullName)
			{
				Name = fullName;
				Version = new Version(0, 0);
			}

			public string Name { get; set; }
			public Version Version { get; set; }

			public override string ToString()
			{
				return Name;
			}
		}
	}
}
